﻿using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

namespace Crm2011.Connect
{
    public class CrmConnectionString
    {
        private readonly Dictionary<string, string> _tokens = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

        public CrmConnectionString() {}


        /// <summary>
        /// Initializes a new instance of the <see cref="CrmConnectionString"/> class.
        /// </summary>
        /// <param name="cns">The CNS.</param>
        public CrmConnectionString(string cns)
        {
            if (string.IsNullOrEmpty(cns)) return;

            string[] cnsTokens = cns.Split(';');
            for (int i = 0; i < cnsTokens.Length; i++)
            {
                cnsTokens[i] = cnsTokens[i].Trim();
                int indexOfEqual = cnsTokens[i].IndexOf('=');
                if (indexOfEqual < 0) continue;
                string key = cnsTokens[i].Substring(0, indexOfEqual);
                string value = cnsTokens[i].Substring(indexOfEqual+1, cnsTokens[i].Length - indexOfEqual-1);
                _tokens[key] = string.IsNullOrEmpty(value)? null:value;
            }

            // if Password token was set, determine if it was a secure or clear-text password
            if (this.Password != null && this.Password.Length > 100)
                this.Password = decryptString(this.Password);
        }



        public Dictionary<string, string> Tokens
        {
            get { return _tokens; }
        }



        /// <summary>
        /// Gets the connection string that is build 
        /// from the current <see cref="Tokens"/>. 
        /// </summary>
        public string GetConnectionStringClearText()
        {
            return getConnectionString(secure: false);
        } 
        
        /// <summary>
        /// Gets the connection string that is build 
        /// from the current <see cref="Tokens"/>. 
        /// </summary>
        public string GetConnectionStringSecure()
        {
            return getConnectionString(secure: true);
        }



        private string getConnectionString(bool secure)
        {
            StringBuilder result = new StringBuilder(128);
            foreach (KeyValuePair<string, string> tokenKeyValue in _tokens)
            {
                if (this.DefaultCredentials && (
                    tokenKeyValue.Key=="Username" ||
                    tokenKeyValue.Key=="Password" || 
                    tokenKeyValue.Key=="Domain"))
                continue;

                result = result.AppendFormat("{0}={1};", tokenKeyValue.Key,
                    (secure && tokenKeyValue.Key == "Password") ? encryptString(tokenKeyValue.Value) : tokenKeyValue.Value);
            }

            return result.ToString();
        }



        #region Default Tokens


        /// <summary>
        /// Gets or sets the Organization Url.
        /// </summary>
        public string Url
        {
            get { return _tokens.ContainsKey("Url") ? _tokens["Url"] : null; }
            set { _tokens["Url"] = value; }
        }


        /// <summary>
        /// Gets or sets the Username.
        /// </summary>
        public string Username
        {
            get { return _tokens.ContainsKey("Username") ? _tokens["Username"] : null; }
            set { _tokens["Username"] = value; }
        }


        /// <summary>
        /// Gets or sets the password as clear text or <c>null</c> if not set.
        /// </summary>
        public string Password
        {
            get { return _tokens.ContainsKey("Password") && !string.IsNullOrEmpty(_tokens["Password"]) ? _tokens["Password"] : null; }
            set { _tokens["Password"] = value; }
        }



        public bool DefaultCredentials
        {
            get { return (this.Password == null); }
        }


        /// <summary>
        /// Gets or sets the Domain.
        /// </summary>
        public string Domain
        {
            get { return _tokens.ContainsKey("Domain") ? _tokens["Domain"] : null; }
            set { _tokens["Domain"] = value; }
        }


        public string DiscoveryUrl
        {
            get { return _tokens.ContainsKey("DiscoveryUrl") && !string.IsNullOrEmpty(_tokens["DiscoveryUrl"]) ? _tokens["DiscoveryUrl"] : null; }
            set { _tokens["DiscoveryUrl"] = value; }
        }


        public string OrganizationUrl
        {
            get { return _tokens.ContainsKey("OrganizationUrl") && !string.IsNullOrEmpty(_tokens["OrganizationUrl"]) ? _tokens["OrganizationUrl"] : null; }
            set { _tokens["OrganizationUrl"] = value; }
        }


        #endregion



        #region Encryption


        private static string encryptString(string value)
        {
            if (string.IsNullOrEmpty(value)) return value;

            byte[] encryptedBytes = ProtectedData.Protect(Encoding.UTF8.GetBytes(value), null, DataProtectionScope.CurrentUser);
            return Convert.ToBase64String(encryptedBytes);
        }



        private static string decryptString(string value)
        {
            if (string.IsNullOrEmpty(value)) return value;

            byte[] decryptedBytes = ProtectedData.Unprotect(Convert.FromBase64String(value), null, DataProtectionScope.CurrentUser);
            return 0 == decryptedBytes.Length ? null : Encoding.UTF8.GetString(decryptedBytes, 0, decryptedBytes.Length);
        }


        #endregion
    }
}